home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Programmer Power Tools
/
Programmer Power Tools.iso
/
cpluspls
/
tasking.cpp
< prev
next >
Wrap
C/C++ Source or Header
|
1991-11-15
|
18KB
|
630 lines
_A C++ Multitasking Kernel_
by Tom Green
[LISTING ONE]
/********************************************/
/* TASK.HPP */
/* Tom Green */
/********************************************/
/* this file contains classes needed to use multi-tasking kernel */
/* include this file in your source code and then link with */
/* task.cpp and timer.asm */
/* this is used when a task is initialized */
/* this is a pointer to a function */
typedef void (*func_ptr)(void);
/* this is used for interrupt handler to call old interrupt handler */
/* this is a far pointer to a function */
typedef void (far *far_func_ptr)(void);
/* this is how the registers will look on the stack for a task */
/* after they have been saved for a task switch. the sp and ss */
/* registers will point to this when a task is started from the */
/* interrupt handler or save_image */
typedef struct task_image{
unsigned int bp;
unsigned int di;
unsigned int si;
unsigned int ds;
unsigned int es;
unsigned int dx;
unsigned int cx;
unsigned int bx;
unsigned int ax;
unsigned int ip;
unsigned int cs;
unsigned int flags;
}task_image;
/* a task object. contains information needed by task_control object */
/* to do task switching and a pointer to the task's workspace (stack) */
class task{
private:
friend class task_control; // task_control object needs access
friend class signal; // signal needs access to next_task
task_image far *stack_ptr; // task stack ("image") pointer
unsigned char task_state; // task state flag
unsigned char *workspace; // address of allocated task stack
task *next_task; // pointer to next task in queue
public:
task(func_ptr func,unsigned int workspace_size); // constructor
~task(); // destructor
};
/* this is a queue for tasks */
/* it is called signal so user can define a signal for task communication */
class signal{
private:
friend class task_control; // task_control needs access
task *head;
task *tail;
task *get_task_q(void); // get next task off of queue
void put_task_q(task *tptr); // append task to queue
public:
signal(void){head=tail=0;}; // constructor
};
/* task_control object */
/* routines and methods to interface with and control tasks */
/* this object will initialize and restore interrupt vectors, */
/* keep track of timer ticks, and switch execution between the */
/* task objects */
class task_control{
private:
signal ready_q; // queue of tasks ready to run
task *current_task; // current active task
task_image far *old_stack_ptr; // return to this stack when done
unsigned int task_running; // task switching enabled flag
unsigned long timer_ticks; // 18.2 ticks/second
unsigned int task_lock; // lock out task switching
task_image far *task_switch(task_image far *stk_ptr,
unsigned int flag,
signal *sig);
public:
task_control(void); // constructor
void add_new_task(task *new_task); // add new task object to ready q
void start_tasks(void); // start switching tasks on ready_q
void stop_tasks(void){task_running=0;};
unsigned long get_timer_ticks(void){return(timer_ticks);};
void lock(void){task_lock=1;}; // current task can not be switched
void unlock(void){task_lock=0;}; // allow task switching
void send(signal *sig); // put task from sig q on ready q
void wait(signal *sig); // put task on sig q
void block(void); // task allows next to run
};
[LISTING TWO]
/********************************************/
/* TASK.CPP */
/* by Tom Green */
/********************************************/
/* this file implements the methods used by task_control and task */
/* objects */
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <int.h>
#include "task.hpp"
/* task states */
#define TASK_INACTIVE 0
#define TASK_ACTIVE 1
#define TASK_READY 2
#define TASK_WAITING 3
#define TASK_ERROR 0xff
/* flags for interface routines */
#define TASK_TIMER_INTR 0
#define TASK_SEND 1
#define TASK_WAIT 2
#define TASK_BLOCK 3
/* system timer interrupt or "timer tick" */
#define TIMER_INT 8
/* routines we need from timer.asm */
unsigned int getCS(void);
extern void timer_handler(void);
extern void save_image(unsigned int flag,signal *sig);
/* global for timer_handler to call old interrupt routine */
far_func_ptr old_timer_handler;
/* this is really ugly. */
/* when constructor for task_control object is called we save the */
/* this pointer for task switch routine to call our task_control object */
/* task_switch. this means we can only have 1 task_control object. sorry */
task_control *gl_tptr;
/* constructor for a new task. workspace will be the stack space for */
/* the task. when the timer interrupt happens the tasks "image" */
/* is saved on the stack for use later and the task_image *stack_ptr */
/* will point to this image */
task::task(func_ptr func,unsigned int workspace_size)
{
task_image *ptr;
/* get stack or "workspace" for task */
if((workspace=(unsigned char *)malloc(workspace_size))==NULL){
task_state=TASK_ERROR; // do not let this one run
return;
}
/* now we must set up the starting "image" of the task registers */
/* ptr will point to the register image to begin task */
ptr=(task_image *)(workspace+workspace_size-sizeof(task_image));
/* now save the pointer to the register image */
stack_ptr=MK_FP(getDS(),(unsigned int)ptr);
ptr->ip=(unsigned int)func; // offset of pointer to task code
ptr->cs=getCS(); // segment of pointer to task, compiler bug
ptr->ds=getDS();
ptr->flags=0x200; // flags, interrupts on
task_state=TASK_INACTIVE; // task is inactive
next_task=0;
/* destructor for a task object */
task::~task(void)
{
free(workspace);
}
/* get the next task off of a task queue */
task *signal::get_task_q(void)
{
task *temp;
temp=head;
if(head)
head=head->next_task;
return(temp);
}
/* append a task to the end of a task queue */
void signal::put_task_q(task *tptr)
{
if(head)
tail->next_task=tptr;
else
head=tptr;
tail=tptr;
tptr->next_task=0;
}
/* constructor for task_control */
/* inits private stuff for task control */
task_control::task_control(void)
{
gl_tptr=this;
task_running=0;
current_task=0;
timer_ticks=0L;
task_lock=0;
}
/* adds a task to the task ready_q */
/* call this routine after creating a task object */
void task_control::add_new_task(task *new_task)
{
if(new_task->task_state!=TASK_ERROR){
new_task->task_state=TASK_READY;
ready_q.put_task_q(new_task);
}
}
/* call to start up tasks after you have created some */
/* and added them to the ready_q */
void task_control::start_tasks(void)
{
unsigned int offset,segment;
task_running=1;
/* get address of old timer interrupt handler */
int_getvector(TIMER_INT,&offset,&segment);
old_timer_handler=(far_func_ptr)(MK_FP(segment,offset));
/* set up our new timer interrupt handler */
int_setvector(TIMER_INT,(unsigned int)timer_handler,getCS());
/* tasks will now start running */
while(task_running)
; // do nothing, trick to wait for tasks to start up
/* falls through to here when multi-tasking is turned off */
}
/* gets the next task off of sig queue and puts it */
/* on the ready_q. this suspends operation of the calling */
/* task which is also put on the ready queue */
void task_control::send(signal *sig)
{
save_image(TASK_SEND,sig);
}
/* puts the calling task on the sig queue to wait for a signal */
void task_control::wait(signal *sig)
{
save_image(TASK_WAIT,sig);
}
/* this causes the current task to be placed on the ready queue */
/* and a switch to the next task on the ready_q */
void task_control::block(void)
{
save_image(TASK_BLOCK,(signal *)0);
}
/* this routine is called to do a task switch. it is */
/* passed a task_image far * to the current stack or task "image". */
/* also pass a flag (described above) and a signal pointer if needed. */
/* a task_image * to the "image" of the next task is returned */
task_image far *task_control::task_switch(task_image far *stk_ptr,
signal *sig)
{
task_image far *temp;
task *tptr;
if(flag==TASK_TIMER_INTR) // increment clock if it is a timer interrupt
timer_ticks++;
/* this saves a pointer to stack when we first start multi-tasking */
/* allows us to return to where start_tasks was called */
if(!current_task){ // no current task so save state for restoring
old_stack_ptr=stk_ptr; // save stack pointer
current_task=ready_q.get_task_q(); // set up a current task
}
/* we have an active task, so do task switch if we can */
if(current_task->task_state==TASK_ACTIVE){
current_task->stack_ptr=stk_ptr; // save stack pointer
current_task->task_state=TASK_READY; // task is ready to go
/* do not allow task switching if tasks are locked and */
/* it is timer interrupt */
if(!task_lock || flag!=TASK_TIMER_INTR){
/* check and see what caused task_switch to be called */
switch(flag){
case TASK_WAIT:
current_task->task_state=TASK_WAITING;
sig->put_task_q(current_task);
break;
case TASK_SEND:
if((tptr=sig->get_task_q())!=0)
ready_q.put_task_q(tptr);
// fall through
case TASK_BLOCK:
case TASK_TIMER_INTR:
current_task->task_state=TASK_READY;
/* put old task on ready queue */
ready_q.put_task_q(current_task);
break;
}
/* get next task to go */
current_task=ready_q.get_task_q();
}
}
/* if we are still multi-tasking, get task ready to run */
if(task_running){
current_task->task_state=TASK_ACTIVE;
temp=current_task->stack_ptr; // get stack pointer of task
}
/* multi-tasking has stopped, get ready to return where we started */
else{ // someone called stop_tasks
int_setvector(TIMER_INT,FP_OFF(old_timer_handler),
FP_SEG(old_timer_handler));
temp=old_stack_ptr; // get back original stack
}
/* return far pointer to stack_image to do task switch */
return(temp);
}
[LISTING THREE]
;*****************************************************************************
; TIMER.ASM
; by Tom Green
; Timer interrupt handler
; Timer interrupt handler calls original handler first and then calls the
; task_control object task switcher. a pointer to the stack "image"
; of the new task is returned by the task switcher.
; getCS
; returns current code segment
; save_image
; saves "image" of task as if interrupt had happened and then calls the
; task_control object task switcher. a pointer to the stack "image"
; of the new task is returned by the task switcher.
;*****************************************************************************
.MODEL SMALL
.8086
.DATA
extrn _old_timer_handler:dword
extrn _gl_tptr:word
extrn __task_control_task_switch:near
.CODE
;*****************************************************************************
; unsigned int getCS(void) - returns current code segment.
; this is needed because of compiler bug. when a function is cast
; to a far function, and you try to get the segment, DS is returned
; instead of CS.
;*****************************************************************************
_getCS proc near
public _getCS
mov ax,cs
ret
_getCS endp
;*****************************************************************************
; timer_handler - this replaces the MS-DOS timer tick interrupt (8H).
; this routine saves everything on stack, calls original timer interrupt
; handler, and then calls task_control object task switcher.
;*****************************************************************************
_timer_handler proc near
public _timer_handler
push ax ;save everything
push bx
push cx
push dx
push es
push ds
push si
push di
push bp
mov bp,dgroup
mov ds,bp ;get our data segment back
pushf
call dword ptr dgroup:_old_timer_handler ;call original handler
sti
xor dx,dx
mov ax,ss
mov bx,sp
push dx ;push 0 for last 2 parameters
push dx ;meaning timer interrupt
push ax
push bx
mov dx,_gl_tptr ;push hidden pointer for C++ object
push dx
;stack is now set up for call to task_control object task_switch
cli ;turn off interrupts for task switch
call __task_control_task_switch
;no need to clean up the stack because it will change
sti
mov ss,dx ;new ss returned in dx
mov sp,ax ;new sp returned in ax
pop bp ;restore registers
pop di
pop si
pop ds
pop es
pop dx
pop cx
pop bx
pop ax
iret
_timer_handler endp
;*****************************************************************************
; void save_image(unsigned int flag,signal *sig) - send, wait, block
; etc. all call through here to save the "image" of the task. this
; code simulates what will happen with an interrupt by saving the task
; image on the stack. the flag passed is passed on to the task_control
; object task switcher so it knows if it was called by the timer
; interrupt handler, send, wait, block, etc. the second parameter
; is a signal pointer which is used by send and wait and is passed
; through to the task switcher.
;*****************************************************************************
_save_image proc near
public _save_image
;since this is a C call we can destroy some registers (ax bx cx dx),
;so now we will set up the stack as if an interrupt call had happened.
;leave parameters on stack, because calling routine will adjust on
;return. bx and cx will have the parameters that were passed.
pop ax ;get return address offset on stack
pop bx ;get first parameter off stack
pop cx ;get second parameter off stack
push cx ;put them back on stack
push bx
pushf ;save flags for iret
mov dx,cs ;get code segment
push dx ;save code segment for return address
push ax ;push saved return address offset
push ax ;save everything
push bx
push cx
push dx
push es
push ds
push si
push di
push bp
sti
mov ax,sp ;stack pointer parameter
push cx ;second parameter passed
push bx ;first parameter passed
mov bx,ss
push bx ;far pointer to stack, parameter passed
push ax
mov ax,_gl_tptr ;push hidden pointer for C++ object
push ax
;stack is now set up for call to task_control object task_switch
cli ;turn off interrupts for task switch
call __task_control_task_switch
;no need to clean up the stack because it will change
sti
mov ss,dx ;new ss returned in dx
mov sp,ax ;new sp returned in ax
pop bp ;restore registers
pop di
pop si
pop ds
pop es
pop dx
pop cx
pop bx
pop ax
iret
_save_image endp
end
[LISTING FOUR]
/********************************************/
/* TASKDEMO.HPP */
/* by Tom Green */
/********************************************/
/* this file is a demonstration of how to use the C++ multi-tasking */
/* kernel. 5 tasks are run and the various means of task switching */
/* and communication are shown */
/* you must have the Zortech C++ compiler version 1.5 and linker and */
/* Microsoft MASM 5.xx to compile this code. */
/* type "ztc taskdemo task timer" and the ztc.com will take */
/* care of compiling, assembling, and linking */
#include <stdio.h>
#include <disp.h>
#include "task.hpp"
void task0(void);
void task1(void);
void task2(void);
void task3(void);
void task4(void);
/* our task_control object (just 1 please) */
task_control tasker;
void main(void)
{
/* task objects */
task t0((func_ptr)task0,1024);
task t1((func_ptr)task1,1024);
task t2((func_ptr)task2,1024);
task t3((func_ptr)task3,1024);
task t4((func_ptr)task4,1024);
/* add task objects to our task_control object ready q */
tasker.add_new_task(&t0);
tasker.add_new_task(&t1);
tasker.add_new_task(&t2);
tasker.add_new_task(&t3);
tasker.add_new_task(&t4);
/* use zortech display package */
disp_open();
disp_move(0,0);
disp_eeop();
/* start tasks up and wait for them to finish */
tasker.start_tasks();
disp_move(0,0);
disp_eeop();
disp_close();
}
static unsigned long counter[]={0L,0L,0L,0L,0L};
static signal sig;
/* task 0 prints the values of the counters for the other 4 tasks. */
/* lock is used to prevent task switching while the screen is being */
/* updated. when the task is finished, block is called to transfer */
/* control to the next task on the ready q */
void task0(void)
{
while(1){
/* disable task switching */
tasker.lock();
disp_move(5,10);
disp_printf("Task 1 %lx",counter[1]);
disp_move(5,50);
disp_printf("Task 2 %lx",counter[2]);
disp_move(15,10);
disp_printf("Task 3 %lx",counter[3]);
disp_move(15,50);
disp_printf("Task 4 %lx",counter[4]);
/* if key pressed then stop the kernel and return */
if(kbhit())
tasker.stop_tasks();
/* re-enable task switching */
tasker.unlock();
/* let next task run */
tasker.block();
}
}
/* tasks 1 and 2 just update counters. these tasks will run until */
/* a timer interrupt occurs, so they get a very large chunk of time */
/* to run, so the counters increase rapidly */
void task1(void)
{
while(1){
counter[1]++;
}
}
void task2(void)
{
while(1){
counter[2]++;
}
}
/* task 3 waits for a signal from task 4 each time the counter is */
/* incremented. when a task waits, it is put on a signal q and the */
/* next task on the ready q is run. this means task 3 and 4 counters */
/* will increment very slowly. in task 4 when a signal is sent, the */
/* task signal q is checked for a task to put on the ready q. the task */
/* sending the signal is then placed on the ready q */
void task3(void)
{
while(1){
counter[3]++;
/* wait for a signal from task 4 */
tasker.wait(&sig);
}
}
void task4(void)
{
while(1){
counter[4]++;
/* send signal to task 3 */
tasker.send(&sig);
}
}